package pkg.networking;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.nio.*;
import java.nio.channels.*;
import java.nio.channels.spi.SelectorProvider;
import javax.swing.Timer;
import pkg.networking.INetworkCallback.CallbackEvent;

public class ClientConnection extends NetworkConnection implements ActionListener {

	private static final int BEATTIME = 1000; //update every half second
	private ArrayList<INetworkCallback> listeners;
	private SocketChannel channel;
	private int uid; //user id
	private String name = "Billy_Bob";
	private Timer heartBeat; //every tim e this timer fires we send syncronization data update's
	
	public ClientConnection()
	{
		listeners = new ArrayList<INetworkCallback>();
		heartBeat = new Timer(BEATTIME,this);
		heartBeat.start();
		log = Logger.getLogger("Bomb.Client");
		log.setUseParentHandlers(true);
	}
	
	public void connect(String ip,int port)
	{
		SocketAddress address = new InetSocketAddress(ip,port);
		try {
			
			selector = SelectorProvider.provider().openSelector();
			
			channel = SocketChannel.open(address);
			channel.configureBlocking(false);
			if(channel.isConnected())
			{
				connected = true;
				
				this.startPump();
				channel.register(selector, SelectionKey.OP_READ);
				selector.wakeup();
				log.log(Level.INFO, "Client Connected to server: " + ip);
			}
			else 
				channel.register(selector, SelectionKey.OP_CONNECT);
		
		
			selector.wakeup();
			
		} catch (IOException e){
			log.log(Level.SEVERE,"Unexpected Connection Error");
		}
	}
	
	@Override //when triggered sends data to notify that we need to syncronizze with server
	public void actionPerformed(ActionEvent arg0) {
		
		RemoteCommand cmdFlags = new RemoteCommand(null,getUserId()); //this command is just flags
		cmdFlags.setFlags(RemoteCommand.Flags.SynchPositions);
		notifyEvent(CallbackEvent.SyncPosition,null);//fake a messige to notify components to send there states
	}
	
	protected void onConnect(SelectionKey key)
	{
		try {
			channel.finishConnect();
			connected = true;
			this.startPump();
			key.interestOps(SelectionKey.OP_READ);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	protected void onRead(SelectionKey key, ByteBuffer dataBuffer)
	{
		if(dataBuffer == null || dataBuffer.capacity() <= 0)
		{
			log.severe("Client Error: Invalid packet recived discarding");
			return;
		}
		   
		try {
			RemoteCommand packet = this.deserializeCommand(dataBuffer);
			if(packet.getInternalCommand() != null)
			System.out.println(packet.getInternalCommand().toString());
			//if(SysCmd.isSys(packet.getInternalCommand()))
				//System.exit(0);
			
			postMessage(packet);
		} catch (Exception e) {
			e.printStackTrace();
			log.severe("Client Error: packet could not be deSerialized.");
			log.warning(e.fillInStackTrace().getMessage());
		}
	}
		
	public void onDisconnect(SelectionKey key)
	{
		try {
			channel.close();
			selector.close();
		} catch (IOException e) {
			log.severe("Unknowen Error");
			log.severe(e.toString());
		}
	}
	
	public void sendMessage(RemoteCommand msg)
	{
		   
	
		   SelectionKey key = channel.keyFor(selector);
	       this.writePacket(key, msg);
	}
	
	private void postMessage(RemoteCommand command)
	{	
		boolean messageHandled = false;
	    if((command.getFlags() & RemoteCommand.Flags.Verrify) == RemoteCommand.Flags.Verrify)
	    {
	    	this.uid = command.getUserId();
	    	log.info("Client Verified by server with " + command.getUserId());
	    	notifyEvent(CallbackEvent.SessionJoined,null);
	    	RemoteCommand cmd = new RemoteCommand(name,this.getUserId());
	    	cmd.setFlags(RemoteCommand.Flags.Verrify);
	    	sendMessage(cmd);	//send our name back
	    	messageHandled = true;
	    }
	    if((command.getFlags() & RemoteCommand.Flags.SynchPositions) == RemoteCommand.Flags.SynchPositions)
	    {
	    	notifyEvent(CallbackEvent.SyncPosition,null);
	    	messageHandled = true;
	    }
	    
	    if(!messageHandled)
	       notifyEvent(CallbackEvent.MessageReceived,command);
	    
	    
	}
	
	private void notifyEvent(CallbackEvent event,Object obj)
	{
		for(INetworkCallback call : listeners)
			call.newtworkEvent(event,obj);
	}
	
	public void registerListener(INetworkCallback call)
	{
		listeners.add(call);
	}
	
	public int getUserId()
	{
		return uid;
	}
	
	public void setName(String name)
	{
		this.name = name;
	}

}
